home *** CD-ROM | disk | FTP | other *** search
- #if 0
- Item 2756811 2-Sept-92 15:57
-
- From: GALWAY@JENSEN.CS.UTAH.EDU@INTERNET#
-
- To: WHITEPALM White Palm, Mike Scanlin,CST
-
- INTERNET# Document Id: <9209022257.AA18389@jensen.cs.utah.edu>
-
- ------------------------------------------------------------------------------
-
- Sub: count-paths.h, count-paths.c
-
- From: galway@jensen.cs.utah.edu
- To: AppleLink.Apple.COM!WHITEPALM@cs.utah.edu
-
- #endif
-
- /* count-paths.c
- *
- * Copyright (C) 1992, William F. Galway
- *
- * Anyone can do what they like with this code, as long as they
- * acknowledge its author, and include this message in their code.
- */
-
- /*
- * This program solves the September 1992 Mac Tutor Challenge problem,
- * the statement of which follows. (Presented by Mike Scanlin.)
- *
- *----------------------------------------------------------------------
- *
- * How Many Ways Can You Spell "CAT"?
- *
- * Given an order N (from 2 to 10) of a graph matrix and a character value for
- * each node in the graph, calculate the number of unique paths that correctly
- * spell out a given word (starting from any node). For instance, if N is 3 and
- * the nodes are C, T, C, A, R, A, T, A and C, your input graph looks
- * like this:
- *
- * C---T---C
- * | | |
- * A---R---A
- * | | |
- * T---A---C
- *
- *
- * Given an input word, say "CAT", you start at each of the 9 nodes and
- * see how many unique paths there are that spell out "CAT". In this
- * example, there are two solutions: (1,1), (2,1), (3,1) and (3,3),
- * (3,2), (3,1). If the input word is "CAR", then there are four
- * solutions: (1,1), (2,1), (2,2) and (1,3), (2,3), (2,2) and (3,3),
- * (2,3), (2,2) and (3,3), (3,2), (2,2). For the input word "CATARACT"
- * there are eight solutions.
- *
- * Each path may cross any node any number of times but you cannot go
- * diagonally from node to node nor can you use the same node twice in a
- * row (in the event that there are two letters next to each other in the
- * input word that are the same). Don't worry about case sensitivity
- * (assume everything is upper or lower case). The input word length is
- * from 1 to 255 (a PString).
- *
- *----------------------------------------------------------------------
- *
- * The algorithm used by this implementation avoids "combinatorial
- * blowup" by working backwards through the input word, keeping a
- * "count table" showing the number of paths for the substring at each
- * node. For example, for the string "CAR" we would get the
- * following counts (count tables) at each stage:
- * -for "r":
- * 0 0 0
- * 0 1 0
- * 0 0 0
- * -for "ar":
- * 0 0 0
- * 1 0 1
- * 0 1 0
- * -for "car":
- * 1 0 1
- * 0 0 0
- * 0 0 2
- * giving a total of 4 solutions found at the final stage.
- * (This non-recursive approach is reminiscent of the iterative versus
- * the recursive method of computing Fibonacci numbers.)
- *
- * We actually keep two count tables around, one giving counts for the
- * "previous stage" (the "previous table"), and one being built up for
- * the "current stage" (the "current table"). We build the current
- * table by locating occurrences of the leading character of the
- * substring, and then summing the counts from the four neighboring
- * locations in the previous table. To ease the problem of dealing
- * with the edges of the tables, we allocate "dummy" rows and columns
- * at the edges of our count tables. The counts at the edges always
- * remain zero, while the interesting stuff goes on in the interior of
- * the tables.
- *
- * To simplify (and speed up) the task of locating occurrences of
- * characters in the matrix, we first build an "index" for the matrix
- * which is basically a linked list of pointers and then index into
- * the index (!) by the character that we need the location(s) of.
- * The index needs building only once for a given matrix, after which
- * the count_paths routine may be called (see how CountPaths invokes
- * count_paths below).
- *
- * Other points to note:
- *
- * -- Use of "Native" pointers for less "pointer arithmetic".
- * -- The result returned by CountPaths is more properly
- * interpreted as an unsigned long rather than as a signed long.
- * -- These routines are not robust when called with matrices
- * of order outside the range 1..MAXORDER.
- */
-
- #include "count-paths.h"
- #include <stdio.h>
-
- #if DEBUG
- /* Return TRUE/FALSE depending on whether index is consistent with */
- /* matrix of given order. Trys to find "all problems", not just */
- /* the first. */
- BOOL CheckIndex(BOOL verbose, long order, const char *matrix,
- const Index *index)
- {
- unsigned char *chrp;
- unsigned long ch;
- LocNode *spot;
- long offset,i,j;
- long delta_y = index->dy;
- long match_count = 0;
- BOOL result = TRUE;
-
- if (delta_y != order+2) {
- if (verbose) {
- printf("Delta-y in index = %ld != %ld = order+2\n",
- delta_y, order+2);
- }
- result = FALSE;
- }
-
- for (ch=0; ch<=255; ch++) {
- for (spot=(index->char_index)[ch].next;
- spot!=NULL;
- spot=spot->next) {
- offset = spot - index->index_matrix;
- i = offset/delta_y - 1;
- j = offset%delta_y - 1;
- if (i<0 || i >= order || j<0 || j>=order) {
- if (verbose) {
- printf("Index points to bad location (%ld,%ld)"
- " in %ld by %ld matrix\n",
- i,j,order,order);
- }
-
- result = FALSE;
- } else {
-
- chrp = (unsigned char *)(matrix + order*i + j);
- if (*chrp != ch) {
- if (verbose) {
- printf("Index says character '%c' at (%ld,%ld)"
- " should be '%c'\n",
- *chrp, i,j, (char)ch);
- }
-
-
- result = FALSE;
- } else {
- match_count++;
- }
- }
- }
- }
-
- if (match_count != order*order) {
- if (verbose) {
- printf("Total of %ld index matches found,"
- " should have been %ld=%ld*%ld\n",
- match_count, order*order, order, order);
- }
-
- result = FALSE;
- }
-
- return result;
- }
-
- BOOL CheckZeroed(BOOL verbose, long tblsize, const long *table)
- {
- const long *tblp;
- BOOL result = TRUE;
-
- for (tblp=table; tblp<table+tblsize;tblp++) {
- if (*tblp != 0) {
- result = FALSE;
- if (verbose) {
- printf("Non-zero count in table[%ld]=%lu\n",
- tblp-table, *tblp);
- }
- }
- }
-
- return result;
- }
- #endif
-
- #if VERBOSE
- void print_counts(const Index *index, const long *count_table)
- {
- long order = index->dy-2;
- long i,j;
- const long *countp;
-
- /* Start just past first row. */
- countp = count_table+order+2;
- i = order-1;
- do {
- /* Skip first column of row. */
- countp++;
- j = order-1;
- do {
- printf(" %lu", *countp++);
- } while (j--);
- /* Skip last column of row. */
- countp++;
- printf("\n");
- } while (i--);
-
- return;
- }
- #endif
-
- /* Build up index for matrix of given order. */
- void BuildIndex(long order, const char *matrix, Index *index)
- {
- register unsigned char *chrp;
- register LocNode *spot, *spot2;
- long i,j;
-
- /* Zero out the char_index (256 entries). */
- spot = index->char_index;
- spot2 = spot+256;
- do {
- (spot++)->next = NULL;
- } while (spot < spot2);
-
- /* Build up the index... The c'th entry in char_index points to */
- /* a chain of pointers residing in index_matrix... Note that */
- /* "edge" rows and columns are allowed to contain nonsense. */
- spot = index->index_matrix+order+3;
- chrp = (unsigned char *)matrix;
- i = order;
- do {
- j = order;
- do {
- /* char_index[char] points to head of chain for char. */
- /* Set spot pointed at to point to "next" spot with ch in it */
- /* (as previously stored in char_index). */
- spot2 = &index->char_index[*chrp++];
- spot->next = spot2->next;
- spot2->next = spot++;
- } while (--j);
- /* Skip last & first columns of row. */
- spot += 2;
- } while (--i);
-
-
- index->dy = order+2;
-
- #if DEBUG
- if (!CheckIndex(TRUE, order, matrix, index)) {
- printf("****CheckIndex failed.****\n");
- }
- #endif
-
- return;
- }
-
- /* Count paths using previously built index. */
- long count_paths(const Index *index, const StringPtr word)
- {
- register unsigned char *chrp;
- register long dyoffset;
- /* tbl_offset gives offset from "current counts" table to */
- /* "previous counts" table. I.e., */
- /* previous_counts = current_counts+tbl_offset. */
- long tbl_offset;
- /* current_offset, previous_offset give offset from index->index_matrix */
- /* to current/previous count tables. */
- register long current_offset;
- register long previous_offset;
- LocNode *spot;
- long *countp;
- register long total;
- long count_tables[2*(2+MAXORDER)*(2+MAXORDER)];
-
- /* Point chrp to last char of word. */
- chrp = word + *word;
-
- /* Initialize misc offsets, pointers. */
- dyoffset = index->dy*sizeof(long);
- /* (short) avoids subroutine call for multiply for some systems. */
- tbl_offset = (short)(index->dy)*(short)dyoffset;
- current_offset = (Native *)count_tables-(Native *)(index->index_matrix);
- previous_offset = tbl_offset+current_offset;
-
- /* Zero out the count tables. */
- countp=count_tables;
- do {
- *countp++ = 0;
- } while (countp < (long *)((Native *)count_tables+2*tbl_offset));
-
- #if DEBUG
- /* Verify that tables were zeroed. */
- if (!CheckZeroed(TRUE, 2*(index->dy)*(index->dy), count_tables)) {
- printf("****CheckZeroed failed.****\n");
- }
- #endif
-
- total = 0;
-
- /* Initialize counts for "previous table". (It will soon be previous!) */
- for (spot=(index->char_index)[*chrp].next; spot!=NULL; spot=spot->next) {
- *(long *)((Native *)spot+previous_offset) = 1;
- total++;
- }
-
- #if VERBOSE
- /* We depend on word being null (0 byte) terminated here. */
- printf("Counts for %s are:\n", chrp);
- print_counts(index,
- (long *)((Native *)(index->index_matrix)
- +previous_offset));
- #endif
-
- if (total==0 || --chrp<=word) {
- return total;
- }
-
- while (TRUE) {
- total = 0;
- for (spot=(index->char_index)[*chrp].next;
- spot!=NULL;
- spot=spot->next) {
- countp = (long *)((Native *)spot + previous_offset);
- /* Hairy expression avoids variable, may free up register... */
- total += *(long *)((Native *)spot + current_offset)
- = *(countp-1)
- + *(countp+1)
- + *(long *)((Native *)countp-dyoffset)
- + *(long *)((Native *)countp+dyoffset);
- }
- #if VERBOSE
- /* We depend on word being null (0 byte) terminated here. */
- printf("Counts for %s are:\n", chrp);
- print_counts(index,
- (long *)((Native *)(index->index_matrix)
- +current_offset));
- #endif
- if (total==0 || --chrp<=word) {
- return total;
- }
-
- /* Swap "current" and "previous" count tables. */
- current_offset += tbl_offset;
- previous_offset -= tbl_offset;
- tbl_offset = - tbl_offset;
-
- /* Zero out current counts, only need touch non-zero entries. */
- for (spot=(index->char_index)[*(chrp+2)].next;
- spot!=NULL;
- spot=spot->next) {
- *(long *)((Native *)spot + current_offset) = 0;
- }
- #if DEBUG
- /* Verify that current counts were zeroed. */
- if (!CheckZeroed(TRUE,
- (index->dy)*(index->dy),
- (long *)((Native *)(index->index_matrix)
- +current_offset))
- ) {
- printf("****CheckZeroed failed.****\n");
- }
- #endif
-
- }
- }
-
- long CountPaths(short order, char *matrix, const StringPtr inputWordPtr)
- {
- long ord=order;
- Index index;
-
- /* Problem statement restricts word length to be >0, but be */
- /* paranoid since count_paths(...) is not robust for 0 length */
- /* words. Return 0 if empty (zero length) word. */
- if (*inputWordPtr == 0) {
- return 0;
- } else if (*inputWordPtr == 1) {
- /* Avoid work of building index, etc. for length one words. */
- register char ch=(char)inputWordPtr[1];
- char *chrp = matrix;
- long total=0;
-
- do {
- if (ch == *chrp++) {
- total++;
- }
- } while (chrp < matrix+order*order);
- return total;
- } else {
- /* Invoke count_paths after building the index... */
- BuildIndex(ord, matrix, &index);
- return count_paths(&index, inputWordPtr);
- }
- }
-